/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2017-2023. All rights reserved.
 * Description: ai stats log utils class
 */

#include "infra/om/stats/ai_stats_log_utils.h"

#include <fstream>
#include <algorithm> // sort
#include <cstdio> // remove

#include "infra/base/api_export.h"
#include "infra/osal/osal_property.h"
#include "infra/om/stats/ai_stats_time.h"

namespace hiai {
// AiStatsLogUtils
std::mutex AiStatsLogUtils::filesNameLock_;
std::mutex AiStatsLogUtils::gDdkVersionLock_;
std::vector<std::string> AiStatsLogUtils::filesName_;

// AiStatsLogCfgUtils
std::map<std::string, std::string> AiStatsLogCfgUtils::statsCfgMap_;
std::mutex AiStatsLogCfgUtils::statsCfgMapLock_;
std::map<AiStatsEngineType, std::string> AiStatsLogCfgUtils::logFileMap_;
std::mutex AiStatsLogCfgUtils::logFileMapLock_;
// 模型管家日志打点开关的状态，原子维护，避免锁带来的开销，
std::map<AiStatsEngineType, std::atomic<bool>> AiStatsLogCfgUtils::isMMLogSwitchOpen_;

namespace {
const char* STATS_CFG_PATH = "/data/vendor/hiai/stats/stats.cfg";
const int MIN_HIAI_STATS_LOG_RESERVE_TIME = 2; // the statistics log reserve min time is 2days,default value
const int MAX_HIAI_STATS_LOG_RESERVE_TIME = 5; // the statistics log reserve max time is 5days
const std::vector<AiStatsEngineType> AI_STATS_ENGINE({
    AI_STATS_HIAI_MNGR, AI_STATS_HIAI_ENGINE, AI_STATS_ANN, AI_STATS_HIAI_HCS, AI_STATS_HIAI_DDK});
} // namespace

void AiStatsLogUtils::GetFileName(const char* dir)
{
    DIR* dp = nullptr;
    struct dirent* entry = nullptr;
    dp = opendir(dir);
    if (dp == nullptr) {
        STATS_LOGD("AiStatsLogUtils::GetFileName opendir[%s] not exist.", dir);
        return;
    }
    while ((entry = readdir(dp)) != nullptr) {
        if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) {
            continue;
        } else if (entry->d_type == 8) { // is file
            std::string tmp(dir);
            tmp.append(entry->d_name); // entry->d_name 仅仅是文件名，这里需要补充为完整全路径
            filesName_.push_back(tmp);
        } else {
            continue;
        }
    }
    closedir(dp);
    return;
}

std::vector<std::string>& AiStatsLogUtils::GetFilesName(const std::string& dir)
{
    std::lock_guard<std::mutex> lk(filesNameLock_);
    filesName_.clear();

    AiStatsLogUtils::GetFileName(dir.c_str());

    return filesName_;
}

std::string AiStatsLogUtils::GetAiDDKVersion()
{
    std::lock_guard<std::mutex> lk(gDdkVersionLock_);

    static char buf[PROP_VALUE_MAX] = {0};
    if (buf[0] != 0) {
        std::string verison(buf);
        return verison;
    }
#ifdef __OHOS__
    const char* vendorVersion = "const.hiai.vendor.hiaiversion";
    const char* configVersion = "const.hiai.config.hiaiversion";
#else
    const char* vendorVersion = "ro.vendor.hiaiversion";
    const char* configVersion = "ro.config.hiaiversion";
#endif

    if (GetProperty(vendorVersion, buf) <= 0) {
        STATS_LOGW("AiStatsLogUtils::GetAiDDKVersion ro.vendor.aiversion parameter not configured.");
        if (GetProperty(configVersion, buf) <= 0) {
            STATS_LOGW("AiStatsLogUtils::GetAiDDKVersion ro.config.aiversion parameter not configured.");
            return "";
        }
    }

    std::string verison(buf);
    return verison;
}

std::string AiStatsLogUtils::GetTodayLogFileName(AiStatsEngineType type)
{
    std::string logPath = AiStatsLogCfgUtils::GetLogPathWithType(type);
    if (logPath.size() == 0) {
        STATS_LOGE("AiStatsLogCfgUtils::GetTodayLogFileName::EngineType[%d]is error.", type);
        return "";
    }

    std::string today = AiStatsTime::Today();

    logPath += today;
    logPath += ".log";
    return logPath;
}

void AiStatsLogUtils::DeleteStatsLog(std::string filename)
{
    if (remove(filename.c_str()) != 0) {
        STATS_LOGI("AiStatsLogUtils::DeleteStatsLog log[ %s ] failed.", filename.c_str());
        return;
    }
    STATS_LOGI("AiStatsLogUtils::DeleteStatsLog log[ %s ] success.", filename.c_str());
    return;
}

AiStatsData AiStatsLogUtils::AddAiStatsData(AiStatsData& data1, AiStatsData& data2)
{
    data1.runTime = (data1.runTime * data1.allCnt + data2.runTime * data2.allCnt) / (data1.allCnt + data2.allCnt);
    data1.allCnt += data2.allCnt;
    data1.failCnt += data2.failCnt;
    for (auto it = data2.result.cbegin(); it != data2.result.cend(); it++) {
        if (data1.result.find(it->first) != data1.result.end()) {
            data1.result[it->first] += it->second;
        } else {
            data1.result[it->first] = it->second;
        }
    }
    if (data1.processName.size() == 0 && data2.processName.size() != 0) {
        data1.processName = data2.processName;
    }
    return data1;
}

std::string AiStatsLogUtils::BuildString(const AiStatsData& statsData)
{
    std::stringstream ss;
    const unsigned int baseChildEngineType = 0x1fff; // add engineType is childEngineType
    ss << "callPkg:" << statsData.callPkg << ";";
    ss << "callTime:" << AiStatsTime::TimeToString(statsData.callTime) << ";";
    ss << "aiddkVersion:" << statsData.ddkVersion << ";";
    ss << "childEngineType:0x" << std::hex << statsData.engineType + baseChildEngineType << ";" << std::dec;
    ss << "interface:" << statsData.interfaceName << ";";
    ss << "allCnt:" << statsData.allCnt << ";";
    ss << "failCnt:" << statsData.failCnt << ";";
    ss << "result:{";
    auto it = statsData.result.cbegin();
    while (it != statsData.result.cend()) {
        ss << it->first << "_" << it->second;
        it++;
        if (it != statsData.result.cend()) {
            ss << ",";
        }
    }
    ss << "};";
    ss << "runTime:" << statsData.runTime << ";";
    ss << "processName:" << statsData.processName << ";";
    return ss.str();
}

std::string AiStatsLogCfgUtils::GetSwitchNameWithType(AiStatsEngineType type)
{
    // The sequence is compiled based on the enum AiStatsEngineType
    const char* switchName[] = {"mngr_stats_log_switch", "engine_stats_log_switch", "ann_stats_log_switch",
        "hcs_stats_log_switch", "ddk_stats_log_switch"};
    int index = type - 1; // array subscript for switchNameMap
    if (index >= 0 && index < static_cast<int>(sizeof(switchName) / sizeof(switchName[0]))) {
        return switchName[index];
    }
    return std::string();
}

std::string AiStatsLogCfgUtils::GetLogPathWithType(AiStatsEngineType type)
{
    // The sequence is compiled based on the enum AiStatsEngineType
    const char* logPath[] = {"/data/vendor/hiai/stats/log/mngr/", "/data/vendor/hiai/stats/log/engine/",
        "/data/vendor/hiai/stats/log/ann/", "/data/vendor/hiai/stats/log/hcs/", "/data/vendor/hiai/stats/log/ddk/"};
    int index = type - 1; // array subscript for switchNameMap
    if (index >= 0 && index < static_cast<int>(sizeof(logPath) / sizeof(logPath[0]))) {
        return logPath[index];
    }
    return std::string();
}

std::string AiStatsLogCfgUtils::GetReserveNameWithType(AiStatsEngineType type)
{
    // The sequence is compiled based on the enum AiStatsEngineType
    const char* reserveNames[] = {"mngr_stats_log_reserve_days", "engine_stats_log_reserve_days",
        "ann_stats_log_reserve_days", "hcs_stats_log_reserve_days", "ddk_stats_log_reserve_days"};
    int index = type - 1; // array subscript for switchNameMap
    if (index >= 0 && index < static_cast<int>(sizeof(reserveNames) / sizeof(reserveNames[0]))) {
        return reserveNames[index];
    }
    return std::string();
}

bool AiStatsLogCfgUtils::UpdateCfgFile()
{
    std::ofstream fs(STATS_CFG_PATH, std::ofstream::out);
    if (!fs.is_open()) {
        STATS_LOGE("AiStatsLogCfgUtils::CreatAndGetCfg create[%s] failed.", STATS_CFG_PATH);
        return false;
    }
    for (auto iter = statsCfgMap_.cbegin(); iter != statsCfgMap_.cend(); iter++) {
        fs << iter->first << ":" << iter->second << std::endl;
    }
    fs.close();
    return true;
}

void AiStatsLogCfgUtils::CreatAndGetDefaultCfg()
{
    statsCfgMap_.clear();
    for (auto item : AI_STATS_ENGINE) {
        statsCfgMap_.insert({AiStatsLogCfgUtils::GetSwitchNameWithType(item), "close"});
        statsCfgMap_.insert({AiStatsLogCfgUtils::GetReserveNameWithType(item), "2"});
        AiStatsLogCfgUtils::isMMLogSwitchOpen_[item] = false;
        STATS_LOGI("AiStatsLogCfgUtils::CreatAndGetDefaultCfg insert[%d] switch name[%s] reserve name [%s].", item,
            AiStatsLogCfgUtils::GetSwitchNameWithType(item).c_str(),
            AiStatsLogCfgUtils::GetReserveNameWithType(item).c_str());
    }
    (void)AiStatsLogCfgUtils::UpdateCfgFile();
}

INFRA_API_EXPORT void AiStatsLogCfgUtils::CreatOrGetCfg()
{
    std::lock_guard<std::mutex> lk(AiStatsLogCfgUtils::statsCfgMapLock_);
    // 先以只读方式打开
    std::fstream fs(STATS_CFG_PATH, std::fstream::in);
    if (!fs.is_open()) { // 打开失败，说明配置文件还不存在
        STATS_LOGI("AiStatsLogCfgUtils::CreatOrGetCfg open[%s] failed.need to create", STATS_CFG_PATH);
        AiStatsLogCfgUtils::CreatAndGetDefaultCfg();
        return;
    }
    statsCfgMap_.clear();
    std::vector<std::string> lines;
    std::string line;
    while (getline(fs, line)) {
        lines.push_back(line);
    }

    for (const auto& cfg : lines) {
        std::size_t pos = cfg.find(":");
        if (pos != std::string::npos) {
            statsCfgMap_.insert({cfg.substr(0, pos), cfg.substr(pos + 1)});
            STATS_LOGI("AiStatsLogCfgUtils::CreatOrGetCfg cfg is[%s,%s]", cfg.substr(0, pos).c_str(),
                cfg.substr(pos + 1).c_str());
        } else {
            STATS_LOGI("AiStatsLogCfgUtils::CreatOrGetCfg can not find \\: in %s", cfg.c_str());
        }
    }
    fs.close();
    AiStatsLogCfgUtils::StoreStatsLogSwitchToAtomicFlag();
    return;
}

void AiStatsLogCfgUtils::StoreStatsLogSwitchToAtomicFlag()
{
    for (auto item : AI_STATS_ENGINE) {
        AiStatsLogCfgUtils::isMMLogSwitchOpen_[item] = false;
        std::map<std::string, std::string>::const_iterator iter =
            statsCfgMap_.find(AiStatsLogCfgUtils::GetSwitchNameWithType(item));
        if (iter != statsCfgMap_.end()) {
            if (iter->second.compare("open") == 0) {
                AiStatsLogCfgUtils::isMMLogSwitchOpen_[item] = true;
            } else {
                STATS_LOGW("AiStatsLogCfgUtils::StoreStatsLogSwitchToAtomicFlag: %s is closed.", iter->first.c_str());
            }
        }
    }
}

int AiStatsLogCfgUtils::GetStatsLogReserveDays(AiStatsEngineType type)
{
    std::lock_guard<std::mutex> lk(AiStatsLogCfgUtils::statsCfgMapLock_);
    std::string key = AiStatsLogCfgUtils::GetReserveNameWithType(type);
    if (key.empty()) {
        return -1;
    }

    std::map<std::string, std::string>::const_iterator iter = statsCfgMap_.find(key);
    if (iter == statsCfgMap_.end()) {
        STATS_LOGI("AiStatsLogCfgUtils::GetStatsLogReserveDays::not has [%s] cfg", key.c_str());
        return -1;
    }
    std::string v = iter->second;

    std::stringstream ss(v);
    int day = 0;
    ss >> day;
    if (day < MIN_HIAI_STATS_LOG_RESERVE_TIME || day > MAX_HIAI_STATS_LOG_RESERVE_TIME) {
        STATS_LOGI("AiStatsLogCfgUtils::GetStatsLogReserveDays::day[%d]is error.", day);
        return -1;
    }
    return day;
}

INFRA_API_EXPORT bool AiStatsLogCfgUtils::WriteStatsLogSwitchSettingToCfg(AiStatsEngineType type, bool isOpen)
{
    std::lock_guard<std::mutex> lk(AiStatsLogCfgUtils::statsCfgMapLock_);
    std::string key = AiStatsLogCfgUtils::GetSwitchNameWithType(type);
    if (key.empty()) {
        return false;
    }
    AiStatsLogCfgUtils::isMMLogSwitchOpen_[type] = isOpen;
    // 1. 刷新配置到内存，配置已存在，则刷新，不存在，则创建
    statsCfgMap_[key] = isOpen ? "open" : "close";
    // 2. 将内存配置数据属性到配置文件中，采用直接覆盖的方法，使得内存配置与文件配置一致
    bool ret = AiStatsLogCfgUtils::UpdateCfgFile();
    STATS_LOGI("AiStatsLogCfgUtils::WriteStatsLogSwitchSettingToCfg:: %s", ret ? "success" : "failed");
    return ret;
}

INFRA_API_EXPORT bool AiStatsLogCfgUtils::WriteStatsLogReserveDaysSettingToCfg(AiStatsEngineType type, uint32_t days)
{
    if (days < MIN_HIAI_STATS_LOG_RESERVE_TIME || days > MAX_HIAI_STATS_LOG_RESERVE_TIME) {
        STATS_LOGI("AiStatsLogCfgUtils::WriteStatsLogReserveDaysSettingToCfg days[%d]is error.", days);
        return false;
    }
    std::lock_guard<std::mutex> lk(AiStatsLogCfgUtils::statsCfgMapLock_);
    std::string key = AiStatsLogCfgUtils::GetReserveNameWithType(type);
    if (key.empty()) {
        return false;
    }

    std::stringstream ss;
    ss << days;

    std::string daysCfg = ss.str();
    // 1. 刷新配置到内存，配置已存在，则刷新，不存在，则创建
    statsCfgMap_[key] = daysCfg;
    // 2. 将内存配置数据属性到配置文件中，采用直接覆盖的方法，使得内存配置与文件配置一致
    bool ret = AiStatsLogCfgUtils::UpdateCfgFile();
    STATS_LOGI("AiStatsLogCfgUtils::WriteStatsLogReserveDaysSettingToCfg:: %s", ret ? "success" : "failed");
    return ret;
}

INFRA_API_EXPORT bool AiStatsLogCfgUtils::HasEngineStatsLog(AiStatsEngineType enginetype)
{
    std::string logPath = AiStatsLogCfgUtils::GetLogPathWithType(enginetype);
    if (logPath.empty()) {
        return false;
    }

    std::vector<std::string> files = AiStatsLogUtils::GetFilesName(logPath);

    STATS_LOGI("AiStatsLogCfgUtils::HasEngineStatsLog::has %d log files.", static_cast<int>(files.size()));
    // 只有一个文件或者没有文件，则认为不存在日志，因为今天的日志是不能返回给client的
    return (static_cast<int>(files.size()) <= 1) ? false : true;
}
/*lint -e713*/
uint32_t AiStatsLogCfgUtils::GetFileSize(std::string filename)
{
    std::ifstream fs(filename, std::ifstream::in);
    if (!fs.is_open()) {
        STATS_LOGI("AiStatsLogCfgUtils::GetFileSize::opne [ %s ] failed.", filename.c_str());
        return 0;
    }
    std::streampos begin;
    std::streampos end;
    begin = fs.tellg();
    fs.seekg(0, std::ios_base::end);
    end = fs.tellg();
    fs.close();
    STATS_LOGI("AiStatsLogCfgUtils::GetFileSize::success, size = %d", static_cast<uint32_t>(end - begin));
    return static_cast<uint32_t>(end - begin);
}

INFRA_API_EXPORT uint32_t AiStatsLogCfgUtils::GetEngineStatsLogSize(AiStatsEngineType enginetype)
{
    std::lock_guard<std::mutex> lk(logFileMapLock_);
    std::string logPath = AiStatsLogCfgUtils::GetLogPathWithType(enginetype);
    if (logPath.empty()) {
        return 0;
    }

    std::vector<std::string> files = AiStatsLogUtils::GetFilesName(logPath);
    if (static_cast<int>(files.size()) < 1) {
        STATS_LOGD("AiStatsLogCfgUtils::GetEngineStatsLogSize:: not has available log");
        return 0;
    }
    // sort the filename in ascending order
    sort(files.begin(), files.end());
    // read file size until not zero, then save the file name
    uint32_t size = 0;
    for (size_t i = 0; i < files.size() - 1; i++) {
        size = AiStatsLogCfgUtils::GetFileSize(files[i]);
        STATS_LOGD("AiStatsLogCfgUtils::GetEngineStatsLogSize:: filepath = %s size = %u", files[i].c_str(), size);
        if (size > 0) {
            logFileMap_[enginetype] = files[i];
            break;
        }
    }
    return size;
}

INFRA_API_EXPORT int32_t AiStatsLogCfgUtils::GetEngineStatsLog(
    char* pStatsLog, uint32_t logSize, AiStatsEngineType enginetype)
{
    std::lock_guard<std::mutex> lk(logFileMapLock_);
    std::map<AiStatsEngineType, std::string>::const_iterator iter = logFileMap_.find(enginetype);
    if (iter == logFileMap_.cend()) {
        STATS_LOGI("AiStatsLogCfgUtils::GetEngineStatsLog enginetype[%d] not exit.", enginetype);
        return -1;
    }
    std::string path = iter->second;

    STATS_LOGI("AiStatsLogCfgUtils::GetEngineStatsLog log path = %s", path.c_str());
    std::ifstream fs(path.c_str(), std::ifstream::binary);
    if (!fs.is_open()) { // open cfg_file failed, the file is not access
        STATS_LOGI("AiStatsLogCfgUtils::GetEngineStatsLog open[%s] failed", path.c_str());
        return -1;
    }
    fs.seekg(0, fs.end);
    uint32_t size = static_cast<uint32_t>(fs.tellg());
    if (size != logSize) {
        STATS_LOGE("AiStatsLogCfgUtils::GetEngineStatsLog failed, reality log size != input logSize");
        logFileMap_.erase(enginetype);
        return -1;
    }
    fs.seekg(0, fs.beg);
    if (fs.read(pStatsLog, size).good()) {
        STATS_LOGE("AiStatsLogCfgUtils::GetEngineStatsLog read file error");
    }
    fs.close();
    return 0;
}

INFRA_API_EXPORT int32_t AiStatsLogCfgUtils::DeleteEngineStatsLog(AiStatsEngineType enginetype)
{
    std::lock_guard<std::mutex> lk(logFileMapLock_);
    const std::map<AiStatsEngineType, std::string>::const_iterator iter = logFileMap_.find(enginetype);
    if (iter == logFileMap_.end()) {
        STATS_LOGW("AiStatsLogCfgUtils::DeleteEngineStatsLog enginetype[%d] not exit.", enginetype);
        return -1;
    }

    std::string path = iter->second;

    if (remove(path.c_str()) != 0) {
        STATS_LOGE("AiStatsLogCfgUtils::DeleteEngineStatsLog log[ %s ] failed.", path.c_str());
        return -1;
    }
    return 0;
}
} // end namespace hiai
